package com.example.demo.mybatis.handler;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.postgresql.util.PGobject;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author liyuncong
 * @version 1.0
 * @file MybatisTypeHandler
 * @brief MybatisTypeHandler
 * @details MybatisTypeHandler
 * @date 2022-11-10
 *
 * Edit History
 * ----------------------------------------------------------------------------
 * DATE                        NAME               DESCRIPTION
 * 2022-11-10                  liyuncong          Created
 */
public class MybatisTypeHandler<T> extends BaseTypeHandler<T> {

    private static final ObjectMapper objectMapper = new ObjectMapper();

    private static final String CLAZZ_TYPE = "_clazz";
    private static final String CLAZZ_DATA = "_data";
    private static final String CLAZZ_METHOD = "getJavaTypeRefForJackson";


    @Override
    public void setNonNullParameter(
        PreparedStatement preparedStatement, int i, T t, JdbcType jdbcType
    ) throws SQLException {
        if (Objects.nonNull(t)) {
            final String content = this.toJson(t);
            final PGobject pGobject = new PGobject();
            pGobject.setType("json");
            pGobject.setValue(content);
            preparedStatement.setObject(i, pGobject);
        } else {
            final PGobject pGobject = new PGobject();
            pGobject.setType("json");
            preparedStatement.setObject(i, pGobject);
        }
    }

    @Override
    public T getNullableResult(ResultSet resultSet, String s) throws SQLException {
        return this.toObject(resultSet.getString(s));
    }

    @Override
    public T getNullableResult(ResultSet resultSet, int i) throws SQLException {
        return this.toObject(resultSet.getString(i));
    }

    @Override
    public T getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        return this.toObject(callableStatement.getString(i));
    }

    private String toJson(T object) {
        try {
            Map<String, Object> temp = new HashMap<>();
            // 分别处理list、map、其他对象
            if (object instanceof Map || object instanceof List) {
                temp.put(CLAZZ_DATA, object);
            } else {
                temp.put(CLAZZ_DATA, object);
                temp.put(CLAZZ_TYPE, object.getClass().getName());
            }
            return objectMapper.writeValueAsString(temp);
        } catch (JsonProcessingException e) {
            throw new RuntimeException();
        }
    }

    private T toObject(String content) {
        if (null == content || Objects.equals("{}", content)) {
            return null;
        }
        try {
            final Map<String, Object> readValue =
                objectMapper.readValue(content.getBytes(StandardCharsets.UTF_8), new TypeReference<>() {
                });
            final String clazzName = (String) readValue.get(CLAZZ_TYPE);
            final String clazzData = objectMapper.writeValueAsString(readValue.get(CLAZZ_DATA));
            // 分别处理list、map、对象
            if (null == clazzName) {
                if (clazzData.startsWith("[")) {
                    return (T) objectMapper.readValue(clazzData, List.class);
                }
                if (clazzData.startsWith("{")) {
                    return (T) objectMapper.readValue(clazzData, Map.class);
                }
                return null;
            } else {
                final Class<?> currentClazz = ClassUtils.forName(clazzName, null);
                final Method staticMethod = ClassUtils.getStaticMethod(currentClazz, CLAZZ_METHOD);
                final TypeReference reference = (TypeReference) staticMethod.invoke(clazzName);
                return (T) objectMapper.readValue(clazzData, reference);
            }
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }
}
